package net.abhinavsarkar.nulllessj;

import java.util.concurrent.Callable;

/**
 * @author Abhinav Sarkar <abhinav@abhinavsarkar.net>
 *
 * @param <T>   Type of the object being wrapped.
 */
public abstract class Maybe<T> {

	/**
	 * Returns if the class is {@link Nothing}
	 */
	abstract public boolean isNothing();
	
	/**
	 * Returns if the class is {@link Just}
	 */
	public boolean isSomething() {
		return !isNothing();
	}

	/**
	 * A convenience shortcut for {@link Maybe#get()}
	 */
	public T $() {
		return get();
	}

	/**
	 * Returns the original wrapped value if the class is {@link Just}.
	 * Throws {@link NothingException} if the class is {@link Nothing}.
	 */
	abstract public T get();

	/**
	 * Returns the original wrapped value if the class is {@link Just}.
	 * Returns the other parameter if the class is {@link Nothing}.
	 * 
	 * @param other	The value to return if the class is {@link Nothing}.
	 */
	public T getOrElse(T other) {
		if (isNothing()) {
			return other;
		} else {
			return get();
		}
	}

	/**
	 * Calls the callable provided if the class is {@link Nothing} and returns
	 * the result of the call wrapped in {@link Maybe}.
	 * If {@link NothingException} or {@link NullPointerException} is thrown
	 * while calling the callable, then returns an object of type 
	 * {@link Nothing}.
	 * If any other exception is thrown while calling the callable, then the
	 * exception is caught and rethrown wrapped in a {@link RuntimeException}.
	 * 
	 * @param <V>      Return type of the callable provided.
	 * @param callable The callable to call
	 * 
	 * @throws RuntimeException See the description. 
	 * 
	 * @see    Maybe#wrap
	 */
	public <V> Maybe<V> callIfNothing(Callable<V> callable) {
		if (isNothing()) {
			try {
				return Maybe.wrap(callable.call());
			} catch (NothingException e) {
				return new Nothing<V>();
			} catch (NullPointerException e) {
	            return new Nothing<V>();
	        } catch (Exception e) {
				throw new RuntimeException(e);
			}
		} else {
			return new Nothing<V>();
		}
	}

	/**
	 * Runs the runnable provided if the class is {@link Nothing}.
     * If {@link NothingException} or {@link NullPointerException} is thrown
     * while running the runnable, then the execution of the runnable terminates 
     * silently and this method returns.
     * If any other exception is thrown while running the runnable, then the
     * exception is caught and rethrown wrapped in a {@link RuntimeException}.
     * 
	 * @param runnable
	 * 
	 * @throws RuntimeException See the description.
	 */
	public void runIfNothing(Runnable runnable) {
		if (isNothing()) {
		    try {
		        runnable.run();
		    } catch (NothingException e) {
		        return;
		    } catch (NullPointerException e) {
		        return;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
		}		
	}

	/**
	 * Calls the callable provided if the class is {@link Just} and returns
     * the result of the call wrapped in {@link Maybe}.
     * If {@link NothingException} or {@link NullPointerException} is thrown
     * while calling the callable, then returns an object of type 
     * {@link Nothing}.
     * If any other exception is thrown while calling the callable, then the
     * exception is caught and rethrown wrapped in a {@link RuntimeException}.
     * 
     * @param <V>      Return type of the callable provided.
     * @param callable The callable to call
     * 
     * @throws RuntimeException See the description. 
     * 
     * @see    Maybe#wrap
	 */
	public <V> Maybe<V> callIfSomething(Callable<V> callable) {
	    if (isSomething()) {
            try {
                return Maybe.wrap(callable.call());
            } catch (NothingException e) {
                return new Nothing<V>();
            } catch (NullPointerException e) {
                return new Nothing<V>();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else {
            return new Nothing<V>();
        }
	}

	/**
	 * Calls the somethingCallable callable or the nothingCallable callable if 
	 * the class is {@link Just} or {@link Nothing} respectively.
	 * Returns the result of the call wrapped in {@link Maybe}.
     * If {@link NothingException} or {@link NullPointerException} is thrown
     * while calling the callables, then returns an object of type 
     * {@link Nothing}.
     * If any other exception is thrown while calling the callables, then the
     * exception is caught and rethrown wrapped in a {@link RuntimeException}.
     * 
     * @param <V>               Return type of the callables provided.
     * @param somethingCallable The callable to call if the class in {@link Just}
     * @param nothingCallable   The callable to call if the class in {@link Nothing}
     * 
     * @throws RuntimeException See the description. 
     * 
     * @see    Maybe#wrap
	 */
	public <V> Maybe<V> callIfSomethingElse(Callable<V> somethingCallable,
			Callable<V> nothingCallable) {
		if (isNothing()) {
			return callIfNothing(nothingCallable);
		} else {
			return callIfSomething(somethingCallable);
		}
	}

	/**
	 * Runs the runnable provided if the class is {@link Just}.
     * If {@link NothingException} or {@link NullPointerException} is thrown
     * while running the runnable, then the execution of the runnable terminates 
     * silently and this method returns.
     * If any other exception is thrown while running the runnable, then the
     * exception is caught and rethrown wrapped in a {@link RuntimeException}.
     * 
     * @param runnable  The runnable to run
     * 
     * @throws RuntimeException See the description.
	 */
	public void runIfSomething(Runnable runnable) {
		if (isSomething()) {
		    try {
                runnable.run();
            } catch (NothingException e) {
                return;
            } catch (NullPointerException e) {
                return;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
		}		
	}

	/**
	 * Runs the somethingRunnable runnable or the nothingRunnable runnable if 
	 * the class is {@link Just} or {@link Nothing} respectively. 
     * If {@link NothingException} or {@link NullPointerException} is thrown
     * while running the runnable, then the execution of the runnable terminates 
     * silently and this method returns.
     * If any other exception is thrown while running the runnable, then the
     * exception is caught and rethrown wrapped in a {@link RuntimeException}.
     * 
     * @param somethingRunnable The runnable to run if the class in {@link Just}
     * @param nothingRunnable   The runnable to run if the class in {@link Nothing}
     * 
     * @throws RuntimeException See the description.
	 */
	public void runIfSomethingElse(Runnable somethingRunnable,
			Runnable nothingRunnable) {
		if (isNothing()) {
			runIfNothing(nothingRunnable);
		} else {
			runIfSomething(somethingRunnable);
		}		
	}
	
	/**
	 * Runs the runnable provided.
     * If {@link NothingException} or {@link NullPointerException} is thrown
     * while running the runnable, then the execution of the runnable terminates 
     * silently and this method returns.
     * If any other exception is thrown while running the runnable, then the
     * exception is caught and rethrown wrapped in a {@link RuntimeException}.
     * 
     * @param runnable  The runnable to run
     * 
     * @throws RuntimeException See the description.
	 */
	public static void run(Runnable runnable) {
		try {
			runnable.run();
		} catch (NothingException e) {
			return;
		} catch (NullPointerException e) {
			return;
		} catch (Exception e) {
            throw new RuntimeException(e);
        }
	}
	
	/**
	 * Calls the callable provided and returns the result of the call wrapped 
	 * in {@link Maybe}.
     * If {@link NothingException} or {@link NullPointerException} is thrown
     * while calling the callable, then returns an object of type 
     * {@link Nothing}.
     * If any other exception is thrown while calling the callable, then the
     * exception is caught and rethrown wrapped in a {@link RuntimeException}.
     * 
     * @param <V>      Return type of the callable provided.
     * @param callable The callable to call
     * 
     * @throws RuntimeException See the description. 
     * 
     * @see    Maybe#wrap
	 */
	public static <V> Maybe<V> call(Callable<V> callable) {
		try {
			return Maybe.wrap(callable.call());
		} catch (NothingException e) {
			return new Nothing<V>();
		} catch (NullPointerException e) {
			return new Nothing<V>();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Wraps an Object in {@link Maybe} and returns the wrapped Object.
	 * If obj is null then returns an instance of {@link Nothing}.
	 * Otherwise returns an instance of {@link Just} wrapped over obj.
	 * 
	 * @param <V>  The type of the object to wrap
	 * @param obj  The object to wrap.
	 */
	public static <V> Maybe<V> wrap(V obj) {
		if (obj == null) {
			return new Nothing<V>();
		}
		return new Just<V>(obj);
	}
}
